package math

import (
	"fmt"
	"math"
	"strconv"
	"strings"
)

type Fractional struct {
	Numerator   int64
	Denominator uint64
}

var ZeroFractional = Fractional{0, 1}
var UInfFractional = Fractional{1, 0}
var IInfFractional = Fractional{-1, 0}

func sign(n int64) int64 {
	if n < 0 {
		return -1
	}
	return 1
}

func MakeFractional(numerator int64, denominator uint64) Fractional {
	f := Fractional{numerator, denominator}
	f.Lose()
	return f
}

func (myt *Fractional) Lose() {
	if myt.Numerator == 0 {
		myt.Denominator = 1
	} else if myt.Denominator != 0 {
		g := GCD(myt.Denominator, AbsInt64(myt.Numerator))
		if g > 1 {
			myt.Numerator /= int64(g)
			myt.Denominator /= g
		}
	} else {
		myt.Numerator = sign(myt.Numerator)
	}
}

func (myt Fractional) Add(r Fractional) Fractional {
	if myt.Numerator == 0 {
		return r
	}
	if r.Numerator == 0 {
		return myt
	}
	if myt.Denominator == 0 || r.Denominator == 0 {
		myt.Denominator, r.Denominator = 0, 0
		myt.Lose()
		r.Lose()
		r = Fractional{myt.Numerator + r.Numerator, 0}
		r.Lose()
		return r
	}
	n := r.Numerator
	d := r.Denominator
	if myt.Denominator != r.Denominator {
		d = LCM(myt.Denominator, r.Denominator)
		n = myt.Numerator*int64(d/myt.Denominator) + r.Numerator*int64(d/r.Denominator)
	} else {
		n += myt.Numerator
	}
	return MakeFractional(n, d)
}

func (myt Fractional) Sub(r Fractional) Fractional {
	r.Numerator = -r.Numerator
	return myt.Add(r)
}

func fractionalMul(n1 int64, d1, n2, d2 uint64) Fractional {
	if n1 == 0 || n2 == 0 {
		return ZeroFractional
	}
	if d1 == 0 || d2 == 0 {
		d1, d2 = 0, 0
		return Fractional{sign(n1), 0}
	}
	g := GCD(AbsInt64(n1), d2)
	if g > 1 {
		n1 /= int64(g)
		d2 /= g
	}
	g = GCD(n2, d1)
	if g > 1 {
		n2 /= g
		d1 /= g
	}
	return MakeFractional(n1*int64(n2), d1*d2)
}

func (myt Fractional) Mul(r Fractional) Fractional {
	if r.Numerator < 0 {
		return fractionalMul(-myt.Numerator, myt.Denominator, uint64(-r.Numerator), r.Denominator)
	}
	return fractionalMul(myt.Numerator, myt.Denominator, uint64(r.Numerator), r.Denominator)
}

func (myt Fractional) Div(r Fractional) Fractional {
	if r.Numerator < 0 {
		return fractionalMul(-myt.Numerator, myt.Denominator, r.Denominator, uint64(-r.Numerator))
	}
	return fractionalMul(myt.Numerator, myt.Denominator, r.Denominator, uint64(r.Numerator))
}

func (myt Fractional) String() string {
	if myt.Numerator == 0 {
		return "0"
	}
	if myt.Denominator == 0 {
		return fmt.Sprintf("%v", math.Inf(int(sign(myt.Numerator))))
	}
	return fmt.Sprintf("%v/%v", myt.Numerator, myt.Denominator)
}

func (myt Fractional) Value() float64 {
	return float64(myt.Numerator) / float64(myt.Denominator)
}

//只显示循环位数
func (myt Fractional) Repeating() string {
	if myt.Numerator == 0 || myt.Denominator == 0 {
		return myt.String()
	}
	var (
		ret string
		n   uint64
		d   uint64 = myt.Denominator
		r   string
	)
	if myt.Numerator < 0 {
		ret = "-"
		n = uint64(-myt.Numerator)
	} else {
		n = uint64(myt.Numerator)
	}
	i := n / d
	ret += strconv.FormatUint(i, 10)
	n = n % d
	nl := make(map[uint64]bool)
	for n != 0 {
		if _, ok := nl[n]; ok {
			break
		}
		nl[n] = true
		n *= 10
		i = n / d
		n = n % d
		switch i {
		case 0:
			r += "0"
		case 1:
			r += "1"
		case 2:
			r += "2"
		case 3:
			r += "3"
		case 4:
			r += "4"
		case 5:
			r += "5"
		case 6:
			r += "6"
		case 7:
			r += "7"
		case 8:
			r += "8"
		case 9:
			r += "9"
		default:
			panic("please forgive me for my ignorance")
		}
	}
	if len(r) > 0 {
		ret += "." + r
	}
	return ret
}

func Float2Fractional(f float64) (Fractional, error) {
	if f == 0 {
		return ZeroFractional, nil
	}
	d := uint64(1)
	for f != float64(int64(f)) {
		if int64(f) < math.MinInt64/10 || int64(f) > math.MaxInt64/10 || d > math.MaxUint64/10 {
			return ZeroFractional, fmt.Errorf("%v :value not support", f)
		}
		f *= 10
		d *= 10
	}
	return MakeFractional(int64(f), d), nil
}

func Str2Fractional(s string) (Fractional, error) {
	n := len(s)
	if n == 0 {
		return Fractional{0, 1}, nil
	}
	sn, sd, ok := strings.Cut(s, "/")
	r := Fractional{0, 1}
	in, er := strconv.ParseInt(sn, 0, 64)
	if er == nil {
		if in == 0 {
			return ZeroFractional, nil
		}
		r.Numerator = in
	} else {
		fn, er2 := strconv.ParseFloat(sn, 0)
		if er2 == nil {
			r, er = Float2Fractional(fn)
			if r.Numerator == 0 {
				return r, nil
			}
		} else {
			return r, er
		}
	}
	if ok {
		id, er := strconv.ParseUint(sd, 0, 64)
		if er == nil {
			if id == 0 {
				return Fractional{sign(r.Numerator), 0}, nil
			}
			r.Denominator *= id
		} else {
			fd, er2 := strconv.ParseFloat(sd, 0)
			if er2 == nil {
				if fd == 0 {
					r.Div(ZeroFractional)
				} else {
					fr, er := Float2Fractional(fd)
					if er != nil {
						return r, er
					}
					r.Div(fr)
				}
			} else {
				return r, er
			}
		}
	}
	return r, nil
}
